/* Copyright (C) 2011 The University of Michigan This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see <http://www.gnu.org/licenses/>. Please send inquiries to powertutor@umich.edu */ package vn.cybersoft.obs.andriod.batterystats2.components; import java.io.File; import java.io.IOException; import java.io.OutputStreamWriter; import vn.cybersoft.obs.andriod.batterystats2.phone.PhoneConstants; import vn.cybersoft.obs.andriod.batterystats2.service.IterationData; import vn.cybersoft.obs.andriod.batterystats2.service.PowerData; import vn.cybersoft.obs.andriod.batterystats2.util.Recycler; import vn.cybersoft.obs.andriod.batterystats2.util.SystemInfo; import android.content.Context; import android.net.wifi.WifiManager; import android.os.SystemClock; import android.util.Log; import android.util.SparseArray; public class Wifi extends PowerComponent { public static class WifiData extends PowerData { private static Recycler<WifiData> recycler = new Recycler<WifiData>(); public static WifiData obtain() { WifiData result = recycler.obtain(); if(result != null) return result; return new WifiData(); } @Override public void recycle() { recycler.recycle(this); } public boolean wifiOn; public double packets; public long uplinkBytes; public long downlinkBytes; public double uplinkRate; public double linkSpeed; public int powerState; private WifiData() { } public void init(double packets, long uplinkBytes, long downlinkBytes, double uplinkRate, double linkSpeed, int powerState) { wifiOn = true; this.packets = packets; this.uplinkBytes = uplinkBytes; this.downlinkBytes = downlinkBytes; this.uplinkRate = uplinkRate; this.linkSpeed = linkSpeed; this.powerState = powerState; } public void init() { wifiOn = false; } public void writeLogDataInfo(OutputStreamWriter out) throws IOException { StringBuilder res = new StringBuilder(); res.append("Wifi-on ").append(wifiOn).append("\n"); if(wifiOn) { res.append("Wifi-packets ").append((long)Math.round(packets)) .append("\nWifi-uplinkBytes ").append(uplinkBytes) .append("\nWifi-downlinkBytes ").append(downlinkBytes) .append("\nWifi-uplink ").append((long)Math.round(uplinkRate)) .append("\nWifi-speed ").append((long)Math.round(linkSpeed)) .append("\nWifi-state ").append(Wifi.POWER_STATE_NAMES[powerState]) .append("\n"); } out.write(res.toString()); } } public static final int POWER_STATE_LOW = 0; public static final int POWER_STATE_HIGH = 1; public static final String[] POWER_STATE_NAMES = {"LOW", "HIGH"}; private static final String TAG = "Wifi"; private PhoneConstants phoneConstants; private WifiManager wifiManager; private SystemInfo sysInfo; private long lastLinkSpeed; private int[] lastUids; private WifiStateKeeper wifiState; private SparseArray<WifiStateKeeper> uidStates; private String transPacketsFile; private String readPacketsFile; private String transBytesFile; private String readBytesFile; private File uidStatsFolder; public Wifi(Context context, PhoneConstants phoneConstants) { this.phoneConstants = phoneConstants; wifiManager = (WifiManager)context.getSystemService(Context.WIFI_SERVICE); sysInfo = SystemInfo.getInstance(); /* Try to grab the interface name. If we can't find it will take a wild * stab in the dark. */ String interfaceName = SystemInfo.getInstance().getProperty("wifi.interface"); if(interfaceName == null) interfaceName = "eth0"; lastLinkSpeed = -1; wifiState = new WifiStateKeeper(phoneConstants.wifiHighLowTransition(), phoneConstants.wifiLowHighTransition()); uidStates = new SparseArray<WifiStateKeeper>(); transPacketsFile = "/sys/devices/virtual/net/" + interfaceName + "/statistics/tx_packets"; readPacketsFile = "/sys/devices/virtual/net/" + interfaceName + "/statistics/rx_packets"; transBytesFile = "/sys/devices/virtual/net/" + interfaceName + "/statistics/tx_bytes"; readBytesFile = "/sys/devices/virtual/net/" + interfaceName + "/statistics/rx_bytes"; uidStatsFolder = new File("/proc/uid_stat"); } @Override public IterationData calculateIteration(long iteration) { IterationData result = IterationData.obtain(); int wifiStateFlag = wifiManager.getWifiState(); if(wifiStateFlag != WifiManager.WIFI_STATE_ENABLED && wifiStateFlag != WifiManager.WIFI_STATE_DISABLING) { /* We need to allow the real iterface state keeper to reset it's state * so that the next update it knows it's coming back from an off state. * We also need to clear all the uid information. */ wifiState.interfaceOff(); uidStates.clear(); lastLinkSpeed = -1; WifiData data = WifiData.obtain(); data.init(); result.setPowerData(data); return result; } long transmitPackets = sysInfo.readLongFromFile(transPacketsFile); long receivePackets = sysInfo.readLongFromFile(readPacketsFile); long transmitBytes = sysInfo.readLongFromFile(transBytesFile); long receiveBytes = sysInfo.readLongFromFile(readBytesFile); if(transmitPackets == -1 || receivePackets == -1 || transmitBytes == -1 || receiveBytes == -1) { /* Couldn't read interface data files. */ Log.w(TAG, "Failed to read packet and byte counts from wifi interface"); return result; } /* Update the link speed every 15 seconds as pulling the WifiInfo structure * from WifiManager is a little bit expensive. This isn't really something * that is likely to change very frequently anyway. */ if(iteration % 15 == 0 || lastLinkSpeed == -1) { lastLinkSpeed = wifiManager.getConnectionInfo().getLinkSpeed(); } double linkSpeed = lastLinkSpeed; if(wifiState.isInitialized()) { wifiState.updateState(transmitPackets, receivePackets, transmitBytes, receiveBytes); WifiData data = WifiData.obtain(); data.init(wifiState.getPackets(), wifiState.getUplinkBytes(), wifiState.getDownlinkBytes(), wifiState.getUplinkRate(), linkSpeed, wifiState.getPowerState()); result.setPowerData(data); } else { wifiState.updateState(transmitPackets, receivePackets, transmitBytes, receiveBytes); } lastUids = sysInfo.getUids(lastUids); if(lastUids != null) for(int uid : lastUids) { if(uid == -1) { continue; } try { WifiStateKeeper uidState = uidStates.get(uid); if(uidState == null) { uidState = new WifiStateKeeper(phoneConstants.wifiHighLowTransition(), phoneConstants.wifiLowHighTransition()); uidStates.put(uid, uidState); } if(!uidState.isStale()) { /* We use a huerstic here so that we don't poll for uids that haven't * had much activity recently. */ continue; } /* These read operations are the expensive part of polling. */ receiveBytes = sysInfo.readLongFromFile( "/proc/uid_stat/" + uid + "/tcp_rcv"); transmitBytes = sysInfo.readLongFromFile( "/proc/uid_stat/" + uid + "/tcp_snd"); if(receiveBytes == -1 || transmitBytes == -1) { Log.w(TAG, "Failed to read uid read/write byte counts"); } else if(uidState.isInitialized()) { /* We only have information about bytes received but what we really * want is the number of packets received so we just have to * estimate it. */ long deltaTransmitBytes = transmitBytes - uidState.getTransmitBytes(); long deltaReceiveBytes = receiveBytes - uidState.getReceiveBytes(); long estimatedTransmitPackets = (long)Math.round(deltaTransmitBytes / wifiState.getAverageTransmitPacketSize()); long estimatedReceivePackets = (long)Math.round(deltaReceiveBytes / wifiState.getAverageReceivePacketSize()); if(deltaTransmitBytes > 0 && estimatedTransmitPackets == 0) { estimatedTransmitPackets = 1; } if(deltaReceiveBytes > 0 && estimatedReceivePackets == 0) { estimatedReceivePackets = 1; } boolean active = transmitBytes != uidState.getTransmitBytes() || receiveBytes != uidState.getReceiveBytes(); uidState.updateState( uidState.getTransmitPackets() + estimatedTransmitPackets, uidState.getReceivePackets() + estimatedReceivePackets, transmitBytes, receiveBytes); if(active) { WifiData uidData = WifiData.obtain(); uidData.init(uidState.getPackets(), uidState.getUplinkBytes(), uidState.getDownlinkBytes(), uidState.getUplinkRate(), linkSpeed, uidState.getPowerState()); result.addUidPowerData(uid, uidData); } } else { uidState.updateState(0, 0, transmitBytes, receiveBytes); } } catch(NumberFormatException e) { Log.w(TAG, "Non-uid files in /proc/uid_stat"); } } return result; } private static class WifiStateKeeper { private long lastTransmitPackets; private long lastReceivePackets; private long lastTransmitBytes; private long lastReceiveBytes; private long lastTime; private int powerState; private double lastPackets; private double lastUplinkRate; private double lastAverageTransmitPacketSize; private double lastAverageReceivePacketSize; private long deltaUplinkBytes; private long deltaDownlinkBytes; private double highLowTransition; private double lowHighTransition; private long inactiveTime; public WifiStateKeeper(double highLowTransition, double lowHighTransition) { this.highLowTransition = highLowTransition; this.lowHighTransition = lowHighTransition; lastTransmitPackets = lastReceivePackets = lastTransmitBytes = lastTime = -1; powerState = POWER_STATE_LOW; lastPackets = lastUplinkRate = 0; lastAverageTransmitPacketSize = 1000; lastAverageReceivePacketSize = 1000; inactiveTime = 0; } public void interfaceOff() { lastTime = SystemClock.elapsedRealtime(); powerState = POWER_STATE_LOW; } public boolean isInitialized() { return lastTime != -1; } public void updateState(long transmitPackets, long receivePackets, long transmitBytes, long receiveBytes) { long curTime = SystemClock.elapsedRealtime(); if(lastTime != -1 && curTime > lastTime) { double deltaTime = curTime - lastTime; lastUplinkRate = (transmitBytes - lastTransmitBytes) / 1024.0 * 7.8125 / deltaTime; lastPackets = receivePackets + transmitPackets - lastReceivePackets - lastTransmitPackets; deltaUplinkBytes = transmitBytes - lastTransmitBytes; deltaDownlinkBytes = receiveBytes - lastReceiveBytes; if(transmitPackets != lastTransmitPackets) { lastAverageTransmitPacketSize = 0.9 * lastAverageTransmitPacketSize + 0.1 * (transmitBytes - lastTransmitBytes) / (transmitPackets - lastTransmitPackets); } if(receivePackets != lastReceivePackets) { lastAverageReceivePacketSize = 0.9 * lastAverageReceivePacketSize + 0.1 * (receiveBytes - lastReceiveBytes) / (receivePackets - lastReceivePackets); } if(receiveBytes != lastReceiveBytes || transmitBytes != lastTransmitBytes) { inactiveTime = 0; } else { inactiveTime += curTime - lastTime; } if(lastPackets < highLowTransition) { powerState = POWER_STATE_LOW; } else if(lastPackets > lowHighTransition) { powerState = POWER_STATE_HIGH; } } lastTime = curTime; lastTransmitPackets = transmitPackets; lastReceivePackets = receivePackets; lastTransmitBytes = transmitBytes; lastReceiveBytes = receiveBytes; } public int getPowerState() { return powerState; } public double getPackets() { return lastPackets; } public long getUplinkBytes() { return deltaUplinkBytes; } public long getDownlinkBytes() { return deltaDownlinkBytes; } public double getUplinkRate() { return lastUplinkRate; } public double getAverageTransmitPacketSize() { return lastAverageTransmitPacketSize; } public double getAverageReceivePacketSize() { return lastAverageReceivePacketSize; } public long getTransmitPackets() { return lastTransmitPackets; } public long getReceivePackets() { return lastReceivePackets; } public long getTransmitBytes() { return lastTransmitBytes; } public long getReceiveBytes() { return lastReceiveBytes; } /* The idea here is that we don't want to have to read uid information * every single iteration for each uid as it just takes too long. So here * we are designing a hueristic that helps us avoid polling for too many * uids. */ public boolean isStale() { long curTime = SystemClock.elapsedRealtime(); return curTime - lastTime > (long)Math.min(10000, inactiveTime); } } private long readLongFromFile(String filePath) { return sysInfo.readLongFromFile(filePath); } @Override public boolean hasUidInformation() { return uidStatsFolder.exists(); } @Override public String getComponentName() { return "Wifi"; } }